package de.stefanocke.roo.wicket.persistence;

import static org.springframework.roo.classpath.customdata.CustomDataKeys.COUNT_ALL_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.FIND_ALL_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.FIND_ENTRIES_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.FIND_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.IDENTIFIER_ACCESSOR_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.MERGE_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.PERSIST_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.REMOVE_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.VERSION_ACCESSOR_METHOD;

import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.classpath.customdata.CustomDataKeys;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.layers.LayerService;
import org.springframework.roo.classpath.layers.LayerType;
import org.springframework.roo.classpath.layers.MemberTypeAdditions;
import org.springframework.roo.classpath.layers.MethodParameter;
import org.springframework.roo.classpath.persistence.PersistenceMemberLocator;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.metadata.MetadataDependencyRegistry;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.support.util.CollectionUtils;

@Component
@Service
public class PersistenceMetadataServiceImpl implements PersistenceMetadataService {

	public static final String ID_PARAMETER_NAME = "id";
	public static final MethodParameter FIRST_RESULT_PARAMETER = new MethodParameter(JavaType.INT_PRIMITIVE,
			"firstResult");
	public static final MethodParameter MAX_RESULTS_PARAMETER = new MethodParameter(JavaType.INT_PRIMITIVE, "sizeNo");
	private static final int LAYER_POSITION = LayerType.HIGHEST.getPosition();

	@Reference
	private MetadataDependencyRegistry metadataDependencyRegistry;
	@Reference
	private MetadataService metadataService;
	@Reference
	private LayerService layerService;
	@Reference
	private PersistenceMemberLocator persistenceMemberLocator;


	/**
	 * Aggregates metadata for a perstistent type and declares a dependency with
	 * it.
	 * 
	 * @param javaType
	 * @param memberDetails
	 * @param metadataIdentificationString
	 * @return
	 */
	@Override
	public PersistenceMetadataDetails getJavaTypePersistenceMetadataDetails(JavaType javaType,
			MemberDetails memberDetails, String metadataIdentificationString) {
		Validate.notNull(javaType, "Java type required");
		Validate.notNull(memberDetails, "Member details required");
		Validate.notEmpty(metadataIdentificationString, "Metadata id required");

		final MethodMetadata idAccessor = memberDetails.getMostConcreteMethodWithTag(IDENTIFIER_ACCESSOR_METHOD);
		if (idAccessor == null) {
			return null;
		}

		final FieldMetadata idField = CollectionUtils.firstElementOf(persistenceMemberLocator
				.getIdentifierFields(javaType));
		if (idField == null) {
			return null;
		}

		final JavaType idType = persistenceMemberLocator.getIdentifierType(javaType);
		if (idType == null) {
			return null;
		}

		registerDependency(idAccessor.getDeclaredByMetadataId(), metadataIdentificationString);
		registerDependency(idField.getDeclaredByMetadataId(), metadataIdentificationString);

		final MethodParameter entityParameter = getEntityParameter(javaType);
		final MethodParameter idParameter = new MethodParameter(idType, ID_PARAMETER_NAME);
		final MethodMetadata versionAccessor = memberDetails.getMostConcreteMethodWithTag(VERSION_ACCESSOR_METHOD);
		final MemberTypeAdditions persistMethod = layerService.getMemberTypeAdditions(metadataIdentificationString,
				PERSIST_METHOD.name(), javaType, idType, LAYER_POSITION, entityParameter);
		final MemberTypeAdditions removeMethod = layerService.getMemberTypeAdditions(metadataIdentificationString,
				REMOVE_METHOD.name(), javaType, idType, LAYER_POSITION, entityParameter);
		final MemberTypeAdditions mergeMethod = layerService.getMemberTypeAdditions(metadataIdentificationString,
				MERGE_METHOD.name(), javaType, idType, LAYER_POSITION, entityParameter);
		final MemberTypeAdditions findAllMethod = layerService.getMemberTypeAdditions(metadataIdentificationString,
				FIND_ALL_METHOD.name(), javaType, idType, LAYER_POSITION);
		final MemberTypeAdditions findMethod = layerService.getMemberTypeAdditions(metadataIdentificationString,
				FIND_METHOD.name(), javaType, idType, LAYER_POSITION, idParameter);
		final MemberTypeAdditions countMethod = layerService.getMemberTypeAdditions(metadataIdentificationString,
				COUNT_ALL_METHOD.name(), javaType, idType, LAYER_POSITION);
		final MemberTypeAdditions findEntriesMethod = layerService.getMemberTypeAdditions(metadataIdentificationString,
				FIND_ENTRIES_METHOD.name(), javaType, idType, LAYER_POSITION, FIRST_RESULT_PARAMETER,
				MAX_RESULTS_PARAMETER);
		final List<String> dynamicFinderNames = memberDetails.getDynamicFinderNames();

		return new PersistenceMetadataDetails(idType, idField, idAccessor, versionAccessor, persistMethod,
				mergeMethod, removeMethod, findAllMethod, findMethod, countMethod, findEntriesMethod,
				dynamicFinderNames, isRooIdentifier(javaType, memberDetails),
				persistenceMemberLocator.getEmbeddedIdentifierFields(javaType));
	}

	public static MethodParameter getEntityParameter(JavaType javaType) {
		return new MethodParameter(javaType, getEntityParameterName(javaType));
	}

	public static JavaSymbolName getEntityParameterName(JavaType javaType) {
		return JavaSymbolName.getReservedWordSafeName(javaType);
	}

	private void registerDependency(String upstreamDependency, String downStreamDependency) {
		if (metadataDependencyRegistry != null
				&& StringUtils.isNotEmpty(upstreamDependency)
				&& StringUtils.isNotEmpty(downStreamDependency)
				&& !upstreamDependency.equals(downStreamDependency)
				&& !MetadataIdentificationUtils.getMetadataClass(downStreamDependency).equals(
						MetadataIdentificationUtils.getMetadataClass(upstreamDependency))) {
			metadataDependencyRegistry.registerDependency(upstreamDependency, downStreamDependency);
		}
	}

	public boolean isRooIdentifier(JavaType javaType, MemberDetails memberDetails) {
		Validate.notNull(javaType, "Java type required");
		Validate.notNull(memberDetails, "Member details required");
		return MemberFindingUtils.getMemberHoldingTypeDetailsWithTag(memberDetails, CustomDataKeys.IDENTIFIER_TYPE)
				.size() > 0;
	}

}
